/*
	This file is part of PropExtractor.

	PropExtractor is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	PropExtractor is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with PropExtractor. If not, see <http://www.gnu.org/licenses/>.
*/

#include "PEIncludes.h"

#include "PEAliceSoftModel.h"
#include "PEAliceSoftImageQNT.h"

#define _USE_MATH_DEFINES
#include <math.h>

#include "PETools.h"

namespace pe
{

	int AliceZippedModelReadHeader(AliceModelZippedHeader* a_Target, PEStream* a_Stream)
	{
		//fread(a_Target, sizeof(AliceModelZippedHeader), 1, a_File->handle);
		PEStreamRead(a_Stream, a_Target, sizeof(AliceModelZippedHeader), 1);

		PE_DEBUG("Zipped header: %s", a_Target->header);

		if (a_Target->header[0] != 'Z' ||
			a_Target->header[1] != 'L' ||
			a_Target->header[2] != 'B')
		{
			return -1;
		}

		return 0;
	}

	int AliceZippedModelDecompress(AliceModelData* a_Target, AliceModelZippedHeader* a_Header, PEStream* a_Stream)
	{
		PE_DEBUG("Model Decompress");

		a_Target->datasize = a_Header->datasize;
		a_Target->data = new byte[a_Target->datasize];
		a_Target->handle = a_Stream;

		z_stream zlib_stream;
		zlib_stream.zalloc = Z_NULL;
		zlib_stream.zfree = Z_NULL;
		zlib_stream.opaque = Z_NULL;
		zlib_stream.avail_in = 0;
		zlib_stream.next_in = Z_NULL;

		inflateInit(&zlib_stream);

		byte* dst = a_Target->data;
		unsigned int have;
		int ret;
		unsigned char in[CHUNK];
		unsigned char out[CHUNK];
		unsigned int total_read = 0;
		unsigned int total_compressed = 0;

		do 
		{
			if (PEStreamRead(a_Stream, in, 1, CHUNK) != PE_OK)
			{
				(void)inflateEnd(&zlib_stream);
				return Z_ERRNO;
			}

			PEStreamGetBytesRead(a_Stream, &zlib_stream.avail_in);

			if (zlib_stream.avail_in == 0) 
			{ 
				break; 
			}
			zlib_stream.next_in = in;

			total_compressed += zlib_stream.avail_in;

			do 
			{
				zlib_stream.avail_out = CHUNK;
				zlib_stream.next_out = out;
				ret = inflate(&zlib_stream, Z_NO_FLUSH);
				switch (ret) 
				{
				case Z_NEED_DICT:
					ret = Z_DATA_ERROR;					

				case Z_DATA_ERROR:
				case Z_MEM_ERROR:
					(void)inflateEnd(&zlib_stream);
					return ret;
				}

				have = CHUNK - zlib_stream.avail_out;
				total_read += have;
				memcpy(dst, out, have);
				dst += have;
			}
			while (zlib_stream.avail_out == 0);
		} 
		while (ret != Z_STREAM_END);

		PE_DEBUG("Read header");

		a_Target->header = new AliceModelHeader;
		a_Target->data += ReadBytes(a_Target->header, sizeof(AliceModelHeader), 1, a_Target->data);

		return 0;
	}

	int AliceZippedModelToObj(const wchar_t* a_ModelPath, const char* a_Pattern /*= NULL*/)
	{
		PEStream read;
		PEStreamOpen(&read, a_ModelPath);

		AliceModelZippedHeader header;
		if (AliceZippedModelReadHeader(&header, &read) != PE_OK)
		{
			PE_PRINT("ERROR! Not an AliceSoft compressed model.");
			return -1;
		}

		PE_DEBUG("Crash?");

		AliceModelData data;
		if (AliceZippedModelDecompress(&data, &header, &read) != PE_OK)
		{
			PE_PRINT("ERROR! Couldn't decompress model.");
			return -1;
		}

		AliceModel model;
		AliceModelDecrypt(&model, &data);

		for (unsigned int i = 0; i < model.material_total; i++)
		{
			WIN32_FIND_DATAW data;
			HANDLE found;

			AliceMaterial* mat = &model.material[i];
			int count = (int)mat->submaterial_total;

			do 
			{
				PE_PRINT("Material %i: %s", i, mat->name);

				for (unsigned int j = 0; j < mat->texture_total; j++)
				{
					wchar_t path[_MAX_PATH] = { 0 };
					//wcstombs(path, read.dir, wcslen(read.dir));
					wcscpy(path, read.dir);

					if (j == 0)
					{
						/*wchar_t temp[_MAX_PATH];
						mbtowc(path, mat->texture_diffuse, strlen(mat->texture_diffuse));
						wcscat(path, temp);*/

						wcscat(path, mat->texture_diffuse);
					}
					else
					{
						/*wchar_t temp[_MAX_PATH];
						mbtowc(path, mat->texture_specular, strlen(mat->texture_specular));
						wcscat(path, temp);*/

						wcscat(path, mat->texture_specular);
					}

					//PE_PRINT("Path %s", path);

					found = FindFirstFileW(path, &data);
					if (found == INVALID_HANDLE_VALUE) { continue; }

					AliceImageQntDecryptAndSave(path);
				}

				if (count > 0)
				{
					mat = &model.material[i].submaterial[mat->submaterial_total - count];
				}
				count--;
			} 
			while (count > 0);
		}

		wchar_t path[_MAX_PATH];
		size_t path_len;
		GetPatternedPath(path, &path_len, "%dir%%name%_uncompressed%ext%", read.path, NULL, 0);

		//PE_DEBUG("Path: %s", path);

		AliceModelSaveToObj(&model, read.path);

		return PE_OK;
	}

	int AliceModelSaveToFile(AliceModelData* a_Data, const wchar_t* a_FilePath, const char* a_Pattern)
	{
		wchar_t path[_MAX_PATH];
		size_t path_len;
		if (a_Pattern)
		{
			GetPatternedPath(path, &path_len, a_Pattern, a_FilePath, NULL, 0);
		}
		else
		{
			GetPatternedPath(path, &path_len, "%workingdir%%name%%ext%", a_FilePath, NULL, 0);
		}

		FILE* write_file = _wfopen(path, L"w+b");
		fwrite(a_Data->data - sizeof(AliceModelHeader), a_Data->datasize, 1, write_file);
		fclose(write_file);

		return 0;
	}

	int AliceModelDecrypt(AliceModel* a_Target, AliceModelData* a_Data)
	{
		if (a_Data->header->header[0] != 'P' ||
			a_Data->header->header[1] != 'O' ||
			a_Data->header->header[2] != 'L'
		)
		{
			return -1;
		}

		PE_DEBUG("Decrypt");

		byte* read = a_Data->data;

		a_Target->header = a_Data->header;
		a_Target->handle = a_Data->handle;
		a_Target->bone_total = 0;

		dword vertexsize = a_Data->header->skin_size;

		char c;

		dword counter = a_Target->header->material_count;

		a_Target->material = new AliceMaterial[a_Target->header->material_count];
		a_Target->material_total = a_Target->header->material_count;

		AliceMaterial* dst_mat = a_Target->material;
		AliceMaterial* prev_mat = dst_mat;

		AliceMaterial* sub_return = NULL;
		unsigned int sub_current = 0;
		unsigned int sub_total = 0;
		bool sub_start = false;

		while (counter > 0)
		{
			if (sub_total > 0)
			{
				PE_DEBUG("-- Submaterial: (%i / %i)", sub_current, sub_return->submaterial_total);
			}
			else
			{
				PE_DEBUG("Materials left: %i", counter);
			}

			// name

			/*char* name;
			read += ReadUTF8String(&name, read);

			dst_mat->index = 0;
			dst_mat->name = new char[strlen(name) + 1];
			strcpy(dst_mat->name, name);*/

			read += ReadUTF8String(&dst_mat->name, read);

			//PE_DEBUG("Name: %s", dst_mat->name);

			//delete name;

			// start reading

			dst_mat->submaterial = NULL;
			dst_mat->submaterial_total = 0;
			dst_mat->texture_diffuse = NULL;
			dst_mat->texture_specular = NULL;
			dst_mat->texture_total = 0;

			unsigned int tex_curr = 0;
			dword textureindex;
			do
			{
				read += ReadBytes(&textureindex, 4, 1, read);

				if (sub_total == 0)
				{
					// check for submaterials

					if (textureindex == 0)
					{
						dword sub_count;
						read += ReadBytes(&sub_count, 4, 1, read);

						PE_DEBUG("Submaterial count: %i", sub_count);

						counter += sub_count;

						dst_mat->submaterial_total = sub_count;
						dst_mat->submaterial = new AliceMaterial[dst_mat->submaterial_total];
						dst_mat->submaterial->texture_diffuse = NULL;
						dst_mat->submaterial->texture_specular = NULL;
						dst_mat->submaterial->texture_total = 0;

						sub_return = dst_mat;
						sub_current = 0;
						sub_total = sub_count;
						sub_start = false;

						dst_mat = &dst_mat->submaterial[0];

						break;
					}
				}
				else
				{
					// no textures for this submaterial
					if (textureindex == 0)
					{
						break;
					}
				}

				// texture

				wchar_t* texturename;
				read += ReadUTF8String(&texturename, read);

				PE_DEBUG("Texture: %s", texturename);

				if (tex_curr == 0)
				{
					//dst_mat->texture_diffuse = new char[strlen(texturename) + 1];
					//strcpy(dst_mat->texture_diffuse, texturename);
					PE_WSTRCLRCPY(dst_mat->texture_diffuse, texturename);
				}
				else
				{
					//dst_mat->texture_specular = new char[strlen(texturename) + 1];
					//strcpy(dst_mat->texture_specular, texturename);
					PE_WSTRCLRCPY(dst_mat->texture_specular, texturename);
				}

				delete texturename;

				tex_curr++;
			} 
			while (textureindex > 1);

			dst_mat->texture_total = tex_curr;
			PE_DEBUG("Total textures: %i", dst_mat->texture_total);

			if (sub_total > 0)
			{
				if (!sub_start) 
				{ 
					sub_start = true;
				}
				else
				{
					dword something2;

					if (tex_curr > 0)
					{
						read += ReadBytes(&something2, 4, 1, read);
						PE_DEBUG("Something2: %i", something2);
					}

					if (++sub_current == sub_total)
					{
						sub_current = 0;
						sub_total = 0;
						sub_start = false;

						dst_mat = sub_return;
						prev_mat = dst_mat;
					}

					dst_mat++;
				}
			}
			else if (tex_curr > 0)
			{
				dword something2;
				read += ReadBytes(&something2, 4, 1, read);
				PE_DEBUG("Something2: %i", something2);

				dword padding;
				ReadBytes(&padding, 4, 1, read);
				if (padding == 0)
				{
					read += 4;
				}

				prev_mat = dst_mat;
				dst_mat++;
			}

			counter--;
		}

		dword mesh_total;
		read += ReadBytes(&mesh_total, 4, 1, read);

		byte padding_check;
		ReadBytes(&padding_check, 1, 1, read);
		if (padding_check == 255)
		{
			while (1)
			{
				ReadBytes(&padding_check, 1, 1, read);
				if (padding_check != 255) { break; }
				read++;
			}
		}

		a_Target->mesh_total = mesh_total;
		a_Target->mesh_filled = 0;
		a_Target->mesh = new AliceMesh[a_Target->mesh_total];

		dword padding1;
		read += ReadBytes(&padding1, 4, 1, read);

		AliceMesh* dst_mesh = a_Target->mesh;
		unsigned int mesh_index;
		for (mesh_index = 0; mesh_index < a_Target->mesh_total; mesh_index++)
		{
			if (mesh_index > 0) 
			{
				byte bone_count_already;
				ReadBytes(&bone_count_already, 1, 1, read);
				if (bone_count_already < 2 || bone_count_already == 255) 
				{ 
					// more padding

					dword skip_amount;
					ReadBytes(&skip_amount, 4, 1, read);
					if (skip_amount > 0) 
					{ 
						read += 4 + (skip_amount * 4);
					}

					while (1)
					{
						ReadBytes(&c, 1, 1, read);
						if (c == -1) { read++; }
						else { break; }
					}
				}

				unsigned int bone_count;
				read += ReadBytes(&bone_count, 4, 1, read);
				if (bone_count > 0)
				{
					a_Target->bone_total = bone_count;
					PE_DEBUG("Bones: %i", bone_count);
					break;
				}
			}

			// mesh

			/*char* meshname;
			read += ReadUTF8String(&meshname, read);
			dst_mesh->name = new char[strlen(meshname) + 1];
			strcpy(dst_mesh->name, meshname);
			delete meshname;*/

			read += ReadUTF8String(&dst_mesh->name, read);

			PE_DEBUG("--- Mesh name: %s", dst_mesh->name);

			// material

			dword material_id;
			read += ReadBytes(&material_id, 4, 1, read);

			dst_mesh->submaterial = -1;
			dst_mesh->material = &a_Target->material[material_id];
			PE_DEBUG("Material: %i, %s", material_id, a_Target->material[material_id].name);

			if (a_Target->material[material_id].submaterial_total > 0)
			{
				PE_DEBUG("Submaterial: (%i / %i)", 
					a_Target->material[material_id].index, 
					a_Target->material[material_id].submaterial_total
				);

				a_Target->material[material_id].index++;
				/*AliceMaterial* mat = a_Target->material[material_id].submaterial;
				for (unsigned int mat_index = 0; mat_index < a_Target->material[material_id].submaterial_total; mat_index++)
				{
					if (!strcmp(mat->name, meshname))
					{
						dst_mesh->submaterial = mat_index;
						break;
					}
					mat++;
				}*/

				dst_mesh->submaterial = 0;
			}

			// vertices

			read += ReadBytes(&dst_mesh->vertex_total, 4, 1, read);

			PE_DEBUG("Vertex total: %i", dst_mesh->vertex_total);

			dst_mesh->skin_total = dst_mesh->vertex_total;
			dst_mesh->skin_data = new AliceSkin[dst_mesh->skin_total];

			dst_mesh->vertex_data = new AliceVertexData[dst_mesh->vertex_total];
			for (unsigned int i = 0; i < dst_mesh->vertex_total; i++)
			{
				read += ReadBytes(&dst_mesh->vertex_data[i], 14, 1, read);

				if (dst_mesh->vertex_data[i].unknown1 > 0)
				{
					dst_mesh->skin_data[i].count = dst_mesh->vertex_data[i].unknown1;
					dst_mesh->skin_data[i].vertex_id = i;

					word unknown2;
					ReadBytes(&unknown2, 2, 1, read);

					if (unknown2 > 0)
					{
						dst_mesh->skin_data[i].data1 = new AliceSkinData1[dst_mesh->skin_data[i].count];

						for (word j = 0; j < dst_mesh->skin_data[i].count; j++)
						{
							word bone_id;
							read += ReadBytes(&bone_id, 2, 1, read);

							if (bone_id > 100)
							{
								int ohi = 1;
							}

							float weight;
							read += ReadBytes(&weight, 4, 1, read);

							//PE_DEBUG("%i %f", bone_id, weight);

							if (weight > 1.f || weight < 0.f)
							{
								int ohi = 1;
							}

							dst_mesh->skin_data[i].data1[j].bone_id = bone_id;
							dst_mesh->skin_data[i].data1[j].weight = weight;
						}
					}
					else
					{
						word id;
						read += ReadBytes(&id, 2, 1, read);

						dst_mesh->skin_data[i].data1 = new AliceSkinData1[dst_mesh->skin_data[i].count];

						for (word j = 0; j < dst_mesh->vertex_data[i].unknown1; j++)
						{
							dword bone_id;
							read += ReadBytes(&bone_id, 4, 1, read);

							float weight;
							read += ReadBytes(&weight, 4, 1, read);

							dst_mesh->skin_data[i].data1[j].bone_id = (word)bone_id;
							dst_mesh->skin_data[i].data1[j].weight = weight;
						}
					}
				}
			}

			read += ReadBytes(&dst_mesh->uv_total, 4, 1, read);
			if (dst_mesh->uv_total >= 0xFFFF)
			{
				PE_DEBUG("Too many coordinates. Corrupt reading.");
				return -1;
			}
			dst_mesh->uv_total2 = 0;

			// texture coordinates

			PE_DEBUG("UV total: %i", dst_mesh->uv_total);

			dst_mesh->uv_data1 = new AliceUVData[dst_mesh->uv_total];
			for (unsigned int i = 0; i < dst_mesh->uv_total; i++)
			{
				read += ReadBytes(&dst_mesh->uv_data1[i], sizeof(AliceUVData), 1, read);
			}

			// padding

			dword pad_something;
			read += ReadBytes(&pad_something, 4, 1, read);

			dword face_total;

			bool something_found = false;
			bool something_else_found = false;

			PE_DEBUG("Pad something: %i", pad_something);

			if (pad_something == 0)
			{
				// skip some stuff

				unsigned int skip_size = 4;

				dword skip_amount;
				read += ReadBytes(&skip_amount, 4, 1, read);

				PE_DEBUG("Skip amount: %i", skip_amount);

				if (skip_amount > 0)
				{
					dword check_size;
					ReadBytes(&check_size, 4, 1, read);
					if (check_size == 0x3f800000) // 1 as a float
					{
						skip_size = 12;
					}

					PE_DEBUG("Skipping %i bytes", skip_amount * skip_size);

					read += skip_amount * skip_size;

					something_found = true;
				}

				if (mesh_index == 127)
				{
					int bleh = 0;
				}

				dword something_count;
				ReadBytes(&something_count, 4, 1, read);
				PE_DEBUG("Something count: %i", something_count);
				if (something_count == 0)
				{
					read += 4;
				}
				else if (skip_amount > 0 && skip_size == 4)
				{
					read += ReadBytes(&skip_amount, 4, 1, read);
					PE_DEBUG("Second skip amount: %i", skip_amount);
					if (skip_amount > 0)
					{
						unsigned int skip_size = 1;

						PE_DEBUG("Skipping %i bytes", skip_amount * skip_size);

						read += skip_amount * skip_size;

						something_found = false;
						something_else_found = true;
					}
				}

				read += ReadBytes(&face_total, 4, 1, read);
			}
			else
			{
				PE_DEBUG("Second set of UV coordinates found");

				something_found = true;

				dst_mesh->uv_total2 = pad_something;

				PE_DEBUG("UV total 2: %i", pad_something);

				dst_mesh->uv_data2 = new AliceUVData[dst_mesh->uv_total2];
				for (unsigned int i = 0; i < dst_mesh->uv_total2; i++)
				{
					read += ReadBytes(&dst_mesh->uv_data2[i], sizeof(AliceUVData), 1, read);
				}

				dword padding_more;
				ReadBytes(&padding_more, 4, 1, read);
				if (padding_more > 0)
				{
					// skip count
					read += 4;

					// skip more padding
					read += padding_more * 4;

					// zeroes
					read += 4;

					// other stuff?
					//read += 8;

					// read faces
					read += ReadBytes(&face_total, 4, 1, read);
				}
				else
				{
					PE_DEBUG("Padding some more");

					dword skip_count;
					while (1)
					{
						ReadBytes(&skip_count, 4, 1, read);
						if (skip_count != 0) { break; }
						read += 4;
					}

					read += ReadBytes(&face_total, 4, 1, read);
				}
			}

			dst_mesh->face_total = face_total;

			// faces

			PE_DEBUG("Face total: %i", dst_mesh->face_total);

			size_t facesize = sizeof(AliceFaceData);

			dst_mesh->face_data = new AliceFaceData[dst_mesh->face_total];
			for (unsigned int face_index = 0; face_index < dst_mesh->face_total; face_index++)
			{
				read += ReadBytes(&dst_mesh->face_data[face_index], sizeof(AliceFaceData), 1, read);

				if (something_else_found)
				{
					// more indices?
					read += 12;
				}
				if (!something_found)
				{
					// no extra data
					read += 40;
				}
				else
				{
					//word something1;
					//ReadBytes(&something1, 2, 1, read);
					if (dst_mesh->uv_total2 > 0)
					{
						// two identifiers?
						read += 4;

						for (unsigned int skip_index = 0; skip_index < 4; skip_index++)
						{
							// some kind of identifier
							read += 12;
						}
					}
					else
					{
						// zeroes
						read += 4;

						for (unsigned int skip_index = 0; skip_index < 3; skip_index++)
						{
							read += 12;
						}
					}
				}
			}

			// check for padding at the end

			while (1)
			{
				ReadBytes(&c, 1, 1, read);
				if (c == -1) { read++; }
				else { break; }
			}

			dst_mesh++;
		}

		PE_DEBUG("Meshes found: %i", mesh_index);

		a_Target->mesh_filled = mesh_index;

		// bones

		//a_Target->bone_total = mesh_total - mesh_index;
		a_Target->bone = NULL;

		if (a_Target->bone_total > 0)
		{
			PE_DEBUG("Bones: %i", a_Target->bone_total);

			a_Target->bone = new AliceBone[a_Target->bone_total];
			AliceBone* dst_bone = a_Target->bone;

			for (unsigned int bone_index = 0; bone_index < a_Target->bone_total; bone_index++)
			{
				/*char* bone_name;
				read += ReadUTF8String(&bone_name, read);

				dst_bone->name = new char[strlen(bone_name) + 1];
				memset(dst_bone->name, 0, strlen(bone_name) + 1);
				strcpy(dst_bone->name, bone_name);*/

				read += ReadUTF8String(&dst_bone->name, read);

				dst_bone->data = new AliceBoneData;
				read += ReadBytes(dst_bone->data, sizeof(AliceBoneData), 1, read);

				//PE_DEBUG("Name: %s - ID: %i", bone_name, dst_bone->data->id);

				while (1)
				{
					ReadBytes(&c, 1, 1, read);
					if (c == -1) { read++; }
					else { break; }
				}

				dst_bone++;
			}

			PE_DEBUG("Bones found: %i", a_Target->bone_total);
		}

		return PE_OK;
	}

	int AliceModelSaveToObj(AliceModel* a_Model, const wchar_t* a_FilePath)
	{
		PEModelRaw raw;

		PEModelRawInit(&raw);
		if (AliceModelToRaw(&raw, a_Model) != PE_OK)
		{
			PEModelRawDelete(&raw);
			return -1;
		}

		if (PEModelRawSaveToObj(&raw, a_FilePath) != PE_OK)
		{
			PEModelRawDelete(&raw);
			return -1;
		}

		PEModelRawDelete(&raw);

		return PE_OK;
	}

	/****************************************
	**
	** Save model to raw
	**
	** converts an AliceSoft model to a Raw model
	** 
	****************************************/

	int AliceModelToRaw(PEModelRaw* a_Target, AliceModel* a_Model)
	{
		AliceMesh* src_mesh;
		PEMeshRaw* dst_mesh;

		// ================================
		// Name
		// ================================

		a_Target->handle = a_Model->handle;
		//a_Target->name = new char[strlen(a_Target->handle->name) + 1];
		//strcpy(a_Target->name, a_Target->handle->name);
		PEStreamGetFileName(a_Target->handle, &a_Target->name);

		// ================================
		// Materials
		// ================================

		a_Target->material_total = a_Model->material_total;
		a_Target->material = new PEMaterialRaw[a_Target->material_total];

		PEMaterialRaw* dst_mat = a_Target->material;
		AliceMaterial* src_mat = a_Model->material;

		unsigned int sub_total = 0;
		bool sub_start = false;
		PEMaterialRaw* dst_mat_pop = NULL;
		AliceMaterial* src_mat_pop = NULL;

		for (unsigned int i = 0; i < a_Target->material_total;)
		{
			PEMaterialRawInit(dst_mat);

			// name

			PE_WSTRCLRCPY(dst_mat->name, src_mat->name);

			// textures

			dst_mat->texture_total = src_mat->texture_total;
			if (src_mat->texture_total > 1)
			{
				//dst_mat->texture_specular = new char[strlen(src_mat->texture_specular) + 1];
				//strcpy(dst_mat->texture_specular, src_mat->texture_specular);

				PE_WSTRCLRCPY(dst_mat->texture_specular, src_mat->texture_specular);
			}
			if (src_mat->texture_total > 0)
			{
				//dst_mat->texture_diffuse = new char[strlen(src_mat->texture_diffuse) + 1];
				//strcpy(dst_mat->texture_diffuse, src_mat->texture_diffuse);

				PE_WSTRCLRCPY(dst_mat->texture_diffuse, src_mat->texture_diffuse);
			}

			// submaterials

			dst_mat->submaterial_total = src_mat->submaterial_total;
			if (src_mat->submaterial_total > 0)
			{
				dst_mat->submaterial = new PEMaterialRaw[dst_mat->submaterial_total];
				sub_total = dst_mat->submaterial_total;

				dst_mat_pop = dst_mat;
				dst_mat = dst_mat->submaterial;

				src_mat_pop = src_mat;
				src_mat = src_mat->submaterial;

				sub_start = false;
			}

			if (sub_total > 0)
			{
				if (sub_start)
				{
					dst_mat++;
					src_mat++;

					if (--sub_total == 0)
					{
						dst_mat = dst_mat_pop;
						src_mat = src_mat_pop;
					}
				}

				sub_start = true;
			}
			
			if (sub_total == 0)
			{
				dst_mat++;
				src_mat++;
				i++;
			}
		}

		// ================================
		// Bones
		// ================================

		a_Target->bone_total = a_Model->bone_total;
		a_Target->bone = new PEBoneRaw[a_Target->bone_total];

		PEBoneRaw* dst_bone = a_Target->bone;
		AliceBone* src_bone = a_Model->bone;

		unsigned int bone_root = 0;

		if (a_Model->bone_total > 0)
		{
			bone_root = src_bone->data->id;
		}

		for (unsigned int i = 0; i < a_Model->bone_total; i++)
		{
			PEBoneRawInit(&a_Target->bone[i]);
		}

		/*float lowest[2] = { 1000.f, 1000.f } ;
		float highest[2] = { -1000.f, -1000.f };*/

		for (unsigned int i = 0; i < a_Model->bone_total; i++)
		{
			//dst_bone->name = new char[strlen(src_bone->name) + 1];
			//strcpy(dst_bone->name, src_bone->name);

			//dst_bone->name = new char[strlen(src_bone->name) + 1];
			
			//StripSpecialCharsAndSave(&dst_bone->name, src_bone->name);
			PE_WSTRCLRCPY(dst_bone->name, src_bone->name);

			/*float min_1 = cosf(src_bone->data->values[3]) * (180.f / M_PI);
			float max_1 = sinf(src_bone->data->values[4]) * (180.f / M_PI);
			float min_2 = sinf(src_bone->data->values[5]) * (180.f / M_PI);
			float max_2 = cosf(src_bone->data->values[6]) * (180.f / M_PI);

			float values[4] = {
				src_bone->data->values[3] * (180.f / M_PI),
				src_bone->data->values[4] * (180.f / M_PI),
				src_bone->data->values[5] * (180.f / M_PI),
				src_bone->data->values[6] * (180.f / M_PI)
			};

			float diff[2] = {
				src_bone->data->values[3] + src_bone->data->values[6],
				src_bone->data->values[4] + src_bone->data->values[5]
			};

			if (diff[0] < lowest[0]) { lowest[0] = diff[0]; }
			if (diff[0] > highest[0]) { highest[0] = diff[0]; }
			if (diff[1] < lowest[0]) { lowest[0] = diff[1]; }
			if (diff[1] > highest[0]) { highest[0] = diff[1]; }*/

			/*if (src_bone->data->values[3] < lowest[0]) { lowest[0] = src_bone->data->values[3]; }
			if (src_bone->data->values[4] < lowest[1]) { lowest[1] = src_bone->data->values[4]; }
			if (src_bone->data->values[3] > highest[0]) { highest[0] = src_bone->data->values[3]; }
			if (src_bone->data->values[4] > highest[1]) { highest[1] = src_bone->data->values[4]; }*/

			dst_bone->localtransform = true;

			dst_bone->inv_bind_position.x = src_bone->data->values[0];
			dst_bone->inv_bind_position.y = src_bone->data->values[1];
			dst_bone->inv_bind_position.z = src_bone->data->values[2];

			/**/

			/*D3DXMATRIX xmat;
			D3DXMatrixIdentity(&xmat);

			D3DXQUATERNION xquat;
			xquat.x = src_bone->data->values[3];
			xquat.y = src_bone->data->values[4];
			xquat.z = src_bone->data->values[5];
			xquat.w = src_bone->data->values[6];

			D3DXMatrixRotationQuaternion(&xmat, &xquat);

			glm::gtc::quaternion::quat rot(
				src_bone->data->values[6], 
				src_bone->data->values[3], 
				src_bone->data->values[4], 
				src_bone->data->values[5]
			);

			D3DXMatrixIdentity(&xmat);
			D3DXMatrixRotationX(&xmat, 15.f);*/

			dst_bone->inv_bind_matrix[1].x = src_bone->data->values[3];
			dst_bone->inv_bind_matrix[2].x = src_bone->data->values[4];
			dst_bone->inv_bind_matrix[1].y = src_bone->data->values[5];
			dst_bone->inv_bind_matrix[2].y = src_bone->data->values[6];

			//dst_bone->inv_bind_matrix = glm::gtc::quaternion::mat4_cast(rot);

			/*glm::gtc::matrix_transform::translate(
				dst_bone->inv_bind_matrix, 
				dst_bone->inv_bind_position
			);*/

			if (src_bone->data->parent_id != 0xFFFFFFFF)
			{
				PE_DEBUG("ID: %i - Parent: %i", 
					src_bone->data->id,
					src_bone->data->parent_id
				);

				AliceBone* src_search = a_Model->bone;
				PEBoneRaw* dst_search = a_Target->bone;

				for (unsigned int j = 0; j < a_Model->bone_total; j++)
				{
					if (src_search->data->id == src_bone->data->parent_id)
					{
						dst_bone->parent = dst_search;
						dst_bone->parent->children_total++;

						break;
					}

					src_search++;
					dst_search++;
				}
			}
			
			dst_bone++;
			src_bone++;
		}

		for (unsigned int i = 0; i < a_Model->bone_total; i++)
		{
			if (a_Target->bone[i].children_total > 0)
			{
				a_Target->bone[i].children = new PEBoneRaw*[a_Target->bone[i].children_total];
			}
		}

		dst_bone = a_Target->bone;
		for (unsigned int i = 0; i < a_Model->bone_total; i++)
		{
			if (dst_bone->parent != NULL)
			{
				dst_bone->parent->children[dst_bone->parent->children_filled] = dst_bone;
				dst_bone->parent->children_filled++;
			}

			dst_bone++;
		}

		// ================================
		// Meshes
		// ================================

		a_Target->mesh_total = a_Model->mesh_filled;
		a_Target->mesh = new PEMeshRaw[a_Target->mesh_total];

		dst_mesh = a_Target->mesh;
		src_mesh = a_Model->mesh;

		for (unsigned int i = 0; i < a_Model->mesh_filled; i++)
		{
			PEMeshRawInit(dst_mesh);

			// name

			//dst_mesh->name = new char[strlen(src_mesh->name) + 1];
			//strcpy(dst_mesh->name, src_mesh->name);
			PE_WSTRCLRCPY(dst_mesh->name, src_mesh->name);

			for (unsigned int j = 0; j < a_Target->material_total; j++)
			{
				if (!wcscmp(a_Target->material[j].name, src_mesh->material->name))
				{
					if (src_mesh->submaterial > -1)
					{
						dst_mesh->material = &a_Target->material[j].submaterial[src_mesh->submaterial];
					}
					else
					{
						dst_mesh->material = &a_Target->material[j];
					}
					break;
				}
			}

			// vertices

			if (src_mesh->vertex_total > 0)
			{
				dst_mesh->vertices = new PEVertexBuffer;
				dst_mesh->vertices->element_size = 3;
				dst_mesh->vertices->total = src_mesh->vertex_total;
				dst_mesh->vertices->position = new float[src_mesh->vertex_total * 3];

				float* dst_vertex = dst_mesh->vertices->position;
				AliceVertexData* src_vertex = src_mesh->vertex_data;
				for (unsigned int j = 0; j < src_mesh->vertex_total; j++)
				{
					dst_vertex[0] = src_vertex->vx;
					dst_vertex[1] = src_vertex->vy;
					dst_vertex[2] = src_vertex->vz;

					dst_vertex += 3;
					src_vertex++;
				}
			}

			// texture coordinates

			if (src_mesh->uv_total > 0)
			{
				dst_mesh->texcoords = new PETexCoordBuffer;
				dst_mesh->texcoords->total = src_mesh->uv_total;
				dst_mesh->texcoords->uv = new float[src_mesh->uv_total * 2];

				float* dst_uv = dst_mesh->texcoords->uv;
				AliceUVData* src_uv = src_mesh->uv_data1;
				for (unsigned int j = 0; j < src_mesh->uv_total; j++)
				{
					dst_uv[0] = src_uv->x;
					dst_uv[1] = src_uv->y * -1.f;

					dst_uv += 2;
					src_uv++;
				}
			}

			// faces

			if (src_mesh->face_total > 0)
			{
				dst_mesh->vertex_indices = new PEIndexBuffer;
				dst_mesh->vertex_indices->element_size = 3;
				dst_mesh->vertex_indices->index = new unsigned int[src_mesh->face_total * 3];
				dst_mesh->vertex_indices->total = src_mesh->face_total;

				dst_mesh->texcoord_indices = new PEIndexBuffer;
				dst_mesh->texcoord_indices->element_size = 3;
				dst_mesh->texcoord_indices->index = new unsigned int[src_mesh->face_total * 3];
				dst_mesh->texcoord_indices->total = src_mesh->face_total;

				AliceFaceData* src_face = src_mesh->face_data;
				unsigned int* dst_v = dst_mesh->vertex_indices->index;
				unsigned int* dst_uv = dst_mesh->texcoord_indices->index;

				for (unsigned int j = 0; j < src_mesh->face_total; j++)
				{
					dst_v[0] = src_face->vertex_index[0];
					dst_v[1] = src_face->vertex_index[1];
					dst_v[2] = src_face->vertex_index[2];

					dst_uv[0] = src_face->uv_index[0];
					dst_uv[1] = src_face->uv_index[1];
					dst_uv[2] = src_face->uv_index[2];

					dst_v += 3;
					dst_uv += 3;
					src_face++;
				}
			}

			dst_mesh++;
			src_mesh++;
		}

		
		// ================================
		// Skin
		// ================================

		// count the amount of weights first

		for (unsigned int i = 0; i < a_Target->mesh_total; i++)
		{
			a_Target->mesh[i].skinboneweights = new PESkinRaw;

			unsigned int total = 0;
			for (unsigned int j = 0; j < a_Model->mesh[i].skin_total; j++)
			{
				total += a_Model->mesh[i].skin_data[j].count;
			}

			a_Target->mesh[i].skinboneweights->skinweight_total = total;
			a_Target->mesh[i].skinboneweights->skinweight = new PESkinWeightRaw[a_Target->mesh[i].skinboneweights->skinweight_total];
		}

		// then write to raw

		src_mesh = a_Model->mesh;
		dst_mesh = a_Target->mesh;

		for (unsigned int i = 0; i < a_Target->mesh_total; i++)
		{
			PEBoneRaw** bones_used = new PEBoneRaw*[src_mesh->skin_total];
			unsigned int bones_filled = 0;

			unsigned int filled = 0;

			for (unsigned int j = 0; j < src_mesh->skin_total; j++)
			{
				for (unsigned int k = 0; k < src_mesh->skin_data[j].count; k++)
				{
					// oh std::map, how i miss thee...

					AliceBone* src_search = a_Model->bone;

					bool found = false;
					for (unsigned int l = 0; l < a_Model->bone_total; l++)
					{
						if (src_search->data->id == src_mesh->skin_data[j].data1[k].bone_id)
						{
							bool unique = true;
							for (unsigned int m = 0; m < bones_filled; m++)
							{
								if (bones_used[m] == &a_Target->bone[l])
								{
									unique = false;
									break;
								}
							}

							if (unique)
							{
								bones_used[bones_filled++] = &a_Target->bone[l];
							}

							dst_mesh->skinboneweights->skinweight[filled].bone_id    = l;
							dst_mesh->skinboneweights->skinweight[filled].bone       = &a_Target->bone[l];
							dst_mesh->skinboneweights->skinweight[filled].vertex_id  = src_mesh->skin_data[j].vertex_id;
							dst_mesh->skinboneweights->skinweight[filled].weight     = src_mesh->skin_data[j].data1[k].weight;

							filled++;

							found = true;
							break;
						}

						src_search++;
					}

					if (!found)
					{
						PE_DEBUG("Couldn't find bone: %i", src_mesh->skin_data[j].data1[k].bone_id);
					}
				}
			}

			PE_DEBUG("Weights filed: %i", filled);

			dst_mesh->bonesused_total = bones_filled;
			dst_mesh->bonesused = new PEBoneRaw*[a_Target->mesh[i].bonesused_total];

			for (unsigned int j = 0; j < bones_filled; j++)
			{
				dst_mesh->bonesused[j] = bones_used[j];

				/*for (unsigned int k = 0; k < filled; k++)
				{
					if (a_Target->mesh[i].skinboneweights->skinweight[k].bone == bones_used[j])
					{
						a_Target->mesh[i].skinboneweights->skinweight[k].bone_id = j;
					}
				}*/
			}

			delete bones_used;

			dst_mesh++;
			src_mesh++;
		}

		return PE_OK;
	}

	int AliceModelReadHeader(AliceModelHeader* a_Target, PEStream* a_Stream)
	{
		//fread(a_Target, sizeof(AliceModelHeader), 1, a_File->handle);
		PEStreamRead(a_Stream, a_Target, sizeof(AliceModelHeader), 1);

		if (a_Target->header[0] != 'P' ||
			a_Target->header[1] != 'O' ||
			a_Target->header[2] != 'L'
		)
		{
			return -1;
		}

		return PE_OK;
	}

	int AliceModelReadData(AliceModelData* a_Data, AliceModelHeader* a_Header, PEStream* a_Stream)
	{
		/*size_t pos_curr = ftell(a_File->handle);

		fseek(a_File->handle, 0, SEEK_END);
		size_t pos_end = ftell(a_File->handle);

		fseek(a_File->handle, pos_curr, SEEK_SET);

		a_Data->datasize = pos_end - pos_curr + sizeof(AliceModelHeader);*/

		a_Data->datasize = a_Stream->filesize;
		a_Data->data = new byte[a_Data->datasize];
		//fread(a_Data->data, 1, a_Data->datasize, a_File->handle);
		PEStreamRead(a_Stream, a_Data->data, 1, a_Data->datasize);

		a_Data->header = a_Header;

		return PE_OK;
	}

	int AliceModelDecryptAndSave(const wchar_t* a_ModelPath, const wchar_t* a_TargetPath, const char* a_Pattern /*= NULL*/)
	{
		PEStream read;
		if (PEStreamOpen(&read, a_ModelPath) != PE_OK)
		{
			PE_PRINT("ERROR! Couldn't open file.");
			return -1;
		}

		AliceModelHeader header;
		if (AliceModelReadHeader(&header, &read) != PE_OK)
		{
			PE_PRINT("ERROR! Not an AliceSoft model file.");
			return -1;
		}

		AliceModelData data;
		AliceModelReadData(&data, &header, &read);

		AliceModel model;
		AliceModelDecrypt(&model, &data);

		AliceModelSaveToObj(&model, a_TargetPath);

		AliceModelDeleteData(&model);

		PEStreamClose(&read);

		//PE_PRINT("Saved decompressed model to '%s.obj'.", a_TargetPath);

		return PE_OK;
	}

	int AliceModelDeleteMaterial(AliceMaterial* a_Material)
	{
		// submaterials

		for (unsigned int i = 0; i < a_Material->submaterial_total; i++)
		{
			AliceModelDeleteMaterial(&a_Material->submaterial[i]);
		}

		// textures

		if (a_Material->texture_total > 1) 
		{
			delete a_Material->texture_specular;
		}
		if (a_Material->texture_total > 0)
		{
			delete a_Material->texture_diffuse;
		}

		// name

		a_Material->name;

		return PE_OK;
	}

	int AliceModelDeleteMesh(AliceMesh* a_Mesh)
	{
		// vertices
		
		delete a_Mesh->vertex_data;

		// uv's

		if (a_Mesh->uv_data1) { delete a_Mesh->uv_data1; }
		if (a_Mesh->uv_data2) { delete a_Mesh->uv_data2; }

		// faces

		delete a_Mesh->face_data;

		// name

		delete a_Mesh->name;

		return PE_OK;
	}

	int AliceModelDeleteData(AliceModel* a_Model)
	{
		// materials

		for (unsigned int i = 0; i < a_Model->material_total; i++)
		{
			AliceModelDeleteMaterial(&a_Model->material[i]);
		}

		// meshes
		
		for (unsigned int i = 0; i < a_Model->mesh_filled; i++)
		{
			AliceModelDeleteMesh(&a_Model->mesh[i]);
		}

		// header

		delete a_Model->header;

		return PE_OK;
	}

}; // namespace pe
